Transaction.getName   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
/**
2
 * @license
3
 * Copyright (c) 2020 UMI
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining a copy
6
 * of this software and associated documentation files (the "Software"), to deal
7
 * in the Software without restriction, including without limitation the rights
8
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
 * copies of the Software, and to permit persons to whom the Software is
10
 * furnished to do so, subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be included in all
13
 * copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
 * SOFTWARE.
22
 */
23
24
'use strict'
25
26 1
const array = require('../util/array.js')
27 1
const sha256 = require('../util/sha256.js')
28 1
const validator = require('../util/validator.js')
29 1
const converter = require('../util/converter.js')
30 1
const Address = require('../address/Address.js')
31 1
const text = require('../util/text.js')
32
33
/**
34
 * Класс для работы с транзакциями.
35
 * @class
36
 */
37
class Transaction {
38
  /**
39
   * @example
40
   * let trx = new Transaction()
41
   */
42
  constructor () {
43
    /**
44
     * Транзакция в бинарном виде.
45
     * @type {number[]}
46
     * @private
47
     */
48 23
    this._bytes = array.arrayNew(150)
49 23
    this.setVersion(Transaction.Basic)
50
  }
51
52
  /**
53
   * Статический метод, создает объект из массива байтов.
54
   * @param {ArrayLike<number>} bytes Транзакция в бинарном виде.
55
   * @returns {Transaction}
56
   * @throws {Error}
57
   * @example
58
   * let bytes = new Uint8Array(150)
59
   * let trx = Transaction.fromBytes(bytes)
60
   */
61
  static fromBytes (bytes) {
62 4
    if (bytes.length !== 150) {
63 1
      throw new Error('invalid length')
64
    }
65 3
    const tx = new Transaction()
66 3
    array.arraySet(tx._bytes, bytes)
67 3
    return tx
68
  }
69
70
  /**
71
   * Транзакция в бинарном виде, 150 байт.
72
   * @returns {number[]}
73
   * @example
74
   * let bytes = new Transaction().getBytes()
75
   */
76
  getBytes () {
77 2
    return this._bytes.slice(0)
78
  }
79
80
  /**
81
   * Хэш (txid) транзакции.
82
   * @returns {number[]}
83
   * @example
84
   * let hash = new Transaction().getHash()
85
   */
86
  getHash () {
87 1
    return sha256.sha256(this._bytes)
88
  }
89
90
  /**
91
   * Версия (тип) транзакции.
92
   * @returns {number}
93
   * @example
94
   * let ver = new Transaction().getVersion()
95
   */
96
  getVersion () {
97 26
    return this._bytes[0]
98
  }
99
100
  /**
101
   * Устанавливает версию и возвращает this.
102
   * @param {number} version Версия (тип) транзакции.
103
   * @returns {Transaction}
104
   * @throws {Error}
105
   * @see Transaction.Genesis
106
   * @see Transaction.Basic
107
   * @see Transaction.CreateStructure
108
   * @see Transaction.UpdateStructure
109
   * @see Transaction.UpdateProfitAddress
110
   * @see Transaction.UpdateFeeAddress
111
   * @see Transaction.CreateTransitAddress
112
   * @see Transaction.DeleteTransitAddress
113
   */
114
  setVersion (version) {
115 31
    validator.validateInt(version, 0, 7)
116 30
    this._bytes[0] = version
117 30
    return this
118
  }
119
120
  /**
121
   * Отправитель.\
122
   * Доступно для всех типов транзакций.
123
   * @returns {Address}
124
   * @example
125
   * let sender = new Transaction().getSender()
126
   */
127
  getSender () {
128 2
    return Address.Address.fromBytes(this._bytes.slice(1, 35))
129
  }
130
131
  /**
132
   * Устанавливает отправителя и возвращает this.
133
   * @param {Address} address Адрес отправителя.
134
   * @returns {Transaction}
135
   * @throws {Error}
136
   * @example
137
   * let sender = new Address()
138
   * let trx = new Transaction().setSender(sender)
139
   */
140
  setSender (address) {
141 3
    array.arraySet(this._bytes, address.getBytes(), 1, 34)
142 2
    return this
143
  }
144
145
  /**
146
   * Получатель.\
147
   * Недоступно для транзакций CreateStructure и UpdateStructure.
148
   * @returns {Address}
149
   * @example
150
   * let recipient = new Transaction().getRecipient()
151
   */
152
  getRecipient () {
153 1
    this.checkVersion([0, 1, 4, 5, 6, 7])
154 1
    return Address.Address.fromBytes(this._bytes.slice(35, 69))
155
  }
156
157
  /**
158
   * Устанавливает получателя и возвращает this.\
159
   * Недоступно для транзакций CreateStructure и UpdateStructure.
160
   * @param {Address} address Адрес получателя.
161
   * @returns {Transaction}
162
   * @throws {Error}
163
   * @example
164
   * let recipient = new Address()
165
   * let trx = new Transaction().setRecipient(recipient)
166
   */
167
  setRecipient (address) {
168 2
    this.checkVersion([0, 1, 4, 5, 6, 7])
169 2
    array.arraySet(this._bytes, address.getBytes(), 35)
170 1
    return this
171
  }
172
173
  /**
174
   * Сумма перевода в UMI-центах, цело число в промежутке от 1 до 18446744073709551615.\
175
   * Доступно только для Genesis и Basic транзакций.
176
   * @returns {number}
177
   * @example
178
   * let value = new Transaction().getValue()
179
   */
180
  getValue () {
181 1
    this.checkVersion([0, 1])
182 1
    return converter.bytesToUint64(this._bytes.slice(69, 77))
183
  }
184
185
  /**
186
   * Устанавливает сумму и возвращает this.\
187
   * Принимает значения в промежутке от 1 до 18446744073709551615.\
188
   * Доступно только для Genesis и Basic транзакций.
189
   * @param {number} value Целое число от 1 до 18446744073709551615.
190
   * @returns {Transaction}
191
   * @throws {Error}
192
   * @example
193
   * let trx = new Transaction().setValue(42)
194
   */
195
  setValue (value) {
196 2
    this.checkVersion([0, 1])
197 2
    validator.validateInt(value, 1, 18446744073709551615)
198 1
    array.arraySet(this._bytes, converter.uint64ToBytes(value), 69)
199 1
    return this
200
  }
201
202
  /**
203
   * Nonce, целое число в промежутке от 0 до 18446744073709551615.\
204
   * Генерируется автоматически при вызове sign().
205
   * @returns {number}
206
   * @example
207
   * let nonce = new Transaction().getNonce()
208
   */
209
  getNonce () {
210 1
    return converter.bytesToUint64(this._bytes.slice(77, 85))
211
  }
212
213
  /**
214
   * Устанавливает nonce и возвращает this.
215
   * @param {number} nonce Целое число в промежутке от 0 до 18446744073709551615.
216
   * @returns {Transaction}
217
   * @throws {Error}
218
   * @example
219
   * let nonce = Date.now()
220
   * let trx = new Transaction().setNonce(nonce)
221
   */
222
  setNonce (nonce) {
223 3
    validator.validateInt(nonce, 0, 18446744073709551615)
224 3
    array.arraySet(this._bytes, converter.uint64ToBytes(nonce), 77)
225 3
    return this
226
  }
227
228
  /**
229
   * Цифровая подпись транзакции, длина 64 байта.
230
   * @returns {number[]}
231
   * @example
232
   * let signature = new Transaction().getSignature()
233
   */
234
  getSignature () {
235 1
    return this._bytes.slice(85, 149)
236
  }
237
238
  /**
239
   * Устанавливает цифровую подпись и возвращает this.
240
   * @param {ArrayLike<number>} signature Подпись, длина 64 байта.
241
   * @returns {Transaction}
242
   * @throws {Error}
243
   * @example
244
   * let signature = new Uint8Array(64)
245
   * let trx = new Transaction().setSignature(signature)
246
   */
247
  setSignature (signature) {
248 2
    if (signature.length !== 64) {
249 1
      throw new Error('invalid length')
250
    }
251 1
    array.arraySet(this._bytes, signature, 85)
252 1
    return this
253
  }
254
255
  /**
256
   * Подписать транзакцию приватным ключом.
257
   * @param {SecretKey} secretKey Приватный ключ.
258
   * @returns {Transaction}
259
   * @throws {Error}
260
   * @example
261
   * let secKey = SecretKey.fromSeed(new Uint8Array(32))
262
   * let trx = new Transaction().sign(secKey)
263
   */
264
  sign (secretKey) {
265 2
    return this.setNonce(new Date().getTime()).setSignature(secretKey.sign(this._bytes.slice(0, 85)))
266
  }
267
268
  /**
269
   * Префикс адресов, принадлежащих структуре.\
270
   * Доступно только для CreateStructure и UpdateStructure.
271
   * @returns {string}
272
   * @throws {Error}
273
   * @example
274
   * let trx = new Transaction().setVersion(Transaction.CreateStructure)
275
   * let prefix = trx.getPrefix()
276
   */
277
  getPrefix () {
278 1
    this.checkVersion([2, 3])
279 1
    return converter.versionToPrefix(converter.bytesToUint16(this._bytes.slice(35, 37)))
280
  }
281
282
  /**
283
   * Устанавливает префикс и возвращает this.\
284
   * Доступно только для CreateStructure и UpdateStructure.
285
   * @param {string} prefix Префикс адресов, принадлежащих структуре.
286
   * @returns {Transaction}
287
   * @throws {Error}
288
   * @example
289
   * let trx = new Transaction().setVersion(CreateStructure)
290
   * trx.setPrefix('aaa')
291
   */
292
  setPrefix (prefix) {
293 2
    this.checkVersion([2, 3])
294 1
    array.arraySet(this._bytes, converter.uint16ToBytes(converter.prefixToVersion(prefix)), 35)
295 1
    return this
296
  }
297
298
  /**
299
   * Название структуры в кодировке UTF-8.\
300
   * Доступно только для CreateStructure и UpdateStructure.
301
   * @returns {string}
302
   * @throws {Error}
303
   * @example
304
   * let trx = new Transaction().setVersion(Transaction.CreateStructure)
305
   * let name = trx.getName()
306
   */
307
  getName () {
308 2
    this.checkVersion([2, 3])
309 2
    if (this._bytes[41] > 35) {
310 1
      throw new Error('invalid length')
311
    }
312 1
    return text.textDecode(this._bytes.slice(42, 42 + this._bytes[41]))
313
  }
314
315
  /**
316
   * Устанавливает название структуры и возвращает this.\
317
   * Доступно только для CreateStructure и UpdateStructure.
318
   * @param {string} name Название структуры в кодировке UTF-8.
319
   * @returns {Transaction}
320
   * @throws {Error}
321
   * @example
322
   * let trx = new Transaction().setVersion(Transaction.CreateStructure)
323
   * trx.setName('Hello World')
324
   */
325
  setName (name) {
326 2
    this.checkVersion([2, 3])
327 2
    const bytes = text.textEncode(name)
328 2
    if (bytes.length > 35) {
329 1
      throw new Error('name is too long')
330
    }
331 1
    array.arraySet(this._bytes, array.arrayNew(36), 41)
332 1
    array.arraySet(this._bytes, bytes, 42)
333 1
    this._bytes[41] = bytes.length
334 1
    return this
335
  }
336
337
  /**
338
   * Профита в сотых долях процента с шагом в 0.01%.\
339
   * Принимает значения от 100 до 500 (соответственно от 1% до 5%).\
340
   * Доступно только для CreateStructure и UpdateStructure.
341
   * @returns {number}
342
   * @example
343
   * let trx = new Transaction().setVersion(Transaction.CreateStructure)
344
   * let profit = trx.getProfitPercent(100)
345
   */
346
  getProfitPercent () {
347 1
    this.checkVersion([2, 3])
348 1
    return converter.bytesToUint16(this._bytes.slice(37, 39))
349
  }
350
351
  /**
352
   * Устанавливает процент профита и возвращает this.\
353
   * Доступно только для CreateStructure и UpdateStructure.
354
   * @param {number} percent Профит в сотых долях процента с шагом в 0.01%.
355
   * Принимает значения от 100 до 500 (соответственно от 1% до 5%).
356
   * @returns {Transaction}
357
   * @throws {Error}
358
   * @example
359
   * let trx = new Transaction().setVersion(Transaction.CreateStructure)
360
   * trx.setProfitPercent(100)
361
   */
362
  setProfitPercent (percent) {
363 2
    this.checkVersion([2, 3])
364 2
    validator.validateInt(percent, 100, 500)
365 1
    array.arraySet(this._bytes, converter.uint16ToBytes(percent), 37)
366 1
    return this
367
  }
368
369
  /**
370
   * Комиссия в сотых долях процента с шагом в 0.01%.\
371
   * Принимает значения от 0 до 2000 (соответственно от 0% до 20%).\
372
   * Доступно только для CreateStructure и UpdateStructure.
373
   * @returns {number}
374
   * @example
375
   * let trx = new Transaction().setVersion(Transaction.CreateStructure)
376
   * let fee = trx.getFeePercent()
377
   */
378
  getFeePercent () {
379 1
    this.checkVersion([2, 3])
380 1
    return converter.bytesToUint16(this._bytes.slice(39, 41))
381
  }
382
383
  /**
384
   * Устанавливает размер комиссии и возвращает this.\
385
   * Доступно только для CreateStructure и UpdateStructure.
386
   * @param {number} percent Комиссия в сотых долях процента с шагом в 0.01%. Принимает значения от 0 до 2000 (соответственно от 0% до 20%).
387
   * @returns {Transaction}
388
   * @throws {Error}
389
   * @example
390
   * let trx = new Transaction().setVersion(Transaction.CreateStructure)
391
   * trx.setFeePercent(100)
392
   */
393
  setFeePercent (percent) {
394 1
    this.checkVersion([2, 3])
395 1
    validator.validateInt(percent, 0, 2000)
396 1
    array.arraySet(this._bytes, converter.uint16ToBytes(percent), 39)
397 1
    return this
398
  }
399
400
  /**
401
   * Проверить транзакцию на соответствие формальным правилам.
402
   * @returns {boolean}
403
   * @throws {Error}
404
   * @example
405
   * let ver = new Transaction().verify()
406
   */
407
  verify () {
408 1
    return this.getSender().getPublicKey().verifySignature(this.getSignature(), this._bytes.slice(0, 85))
409
  }
410
411
  /**
412
   * @param {number[]} versions
413
   * @throws {Error}
414
   * @private
415
   */
416
  checkVersion (versions) {
417 18
    for (let i = 0, l = versions.length; i < l; i++) {
418 25
      if (versions[i] === this.getVersion()) {
419 17
        return
420
      }
421
    }
422 1
    throw new Error('invalid version')
423
  }
424
}
425
/**
426
 * Genesis-транзакция.\
427
 * Может быть добавлена только в Genesis-блок.\
428
 * Адрес отправителя должен иметь префикс genesis, адрес получателя - umi.
429
 * @type {number}
430
 * @constant
431
 * @example
432
 * let secKey = SecretKey.fromSeed(new Uint8Array(32))
433
 * let sender = Address.fromKey(secKey).setPrefix('genesis')
434
 * let recipient = Address.fromKey(secKey).setPrefix('umi')
435
 * let tx = new Transaction()
436
 *   .setVersion(Transaction.Genesis)
437
 *   .setSender(sender)
438
 *   .setRecipient(recipient)
439
 *   .setValue(42)
440
 *   .sign(secKey)
441
 */
442 1
Transaction.Genesis = 0
443
/**
444
 * Стандартная транзакция. Перевод монет из одного кошелька в другой.
445
 * @type {number}
446
 * @constant
447
 * @example
448
 * let secKey = SecretKey.fromSeed(new Uint8Array(32))
449
 * let sender = Address.fromKey(secKey).setPrefix('umi')
450
 * let recipient = Address.fromKey(secKey).setPrefix('aaa')
451
 * let tx = new Transaction()
452
 *   .setVersion(Transaction.Basic)
453
 *   .setSender(sender)
454
 *   .setRecipient(recipient)
455
 *   .setValue(42)
456
 *   .sign(secKey)
457
 */
458 1
Transaction.Basic = 1
459
/**
460
 * Создание новой структуры.
461
 * @type {number}
462
 * @constant
463
 * @example
464
 * let secKey = SecretKey.fromSeed(new Uint8Array(32))
465
 * let sender = Address.fromKey(secKey).setPrefix('umi')
466
 * let tx = new Transaction()
467
 *   .setVersion(Transaction.CreateStructure)
468
 *   .setSender(sender)
469
 *   .setPrefix('aaa')
470
 *   .setName('My Struct 🙂')
471
 *   .setProfitPercent(100)
472
 *   .setFeePercent(0)
473
 *   .sign(secKey)
474
 */
475 1
Transaction.CreateStructure = 2
476
/**
477
 * Обновление настроек существующей структуры.
478
 * @type {number}
479
 * @constant
480
 * @example
481
 * let secKey = SecretKey.fromSeed(new Uint8Array(32))
482
 * let sender = Address.fromKey(secKey).setPrefix('umi')
483
 * let tx = new Transaction()
484
 *   .setVersion(Transaction.UpdateStructure)
485
 *   .setSender(sender)
486
 *   .setPrefix('aaa')
487
 *   .setName('My New Struct 😎')
488
 *   .setProfitPercent(500)
489
 *   .setFeePercent(2000)
490
 *   .sign(secKey)
491
 */
492 1
Transaction.UpdateStructure = 3
493
/**
494
 * Изменение адреса для начисления профита.
495
 * @type {number}
496
 * @constant
497
 * @example
498
 * let secKey = SecretKey.fromSeed(new Uint8Array(32))
499
 * let sender = Address.fromKey(secKey).setPrefix('umi')
500
 * let newPrf = Address.fromBech32('aaa18d4z00xwk6jz6c4r4rgz5mcdwdjny9thrh3y8f36cpy2rz6emg5svsuw66')
501
 * let tx = new Transaction()
502
 *   .setVersion(Transaction.UpdateProfitAddress)
503
 *   .setSender(sender)
504
 *   .setRecipient(newPrf)
505
 *   .sign(secKey)
506
 */
507 1
Transaction.UpdateProfitAddress = 4
508
/**
509
 * Изменение адреса на который переводится комиссия.
510
 * @type {number}
511
 * @constant
512
 * @example
513
 * let secKey = SecretKey.fromSeed(new Uint8Array(32))
514
 * let sender = Address.fromKey(secKey).setPrefix('umi')
515
 * let newFee = Address.fromBech32('aaa18d4z00xwk6jz6c4r4rgz5mcdwdjny9thrh3y8f36cpy2rz6emg5svsuw66')
516
 * let tx = new Transaction()
517
 *   .setVersion(Transaction.UpdateFeeAddress)
518
 *   .setSender(sender)
519
 *   .setRecipient(newFee)
520
 *   .sign(secKey)
521
 */
522 1
Transaction.UpdateFeeAddress = 5
523
/**
524
 * Активация транзитного адреса.
525
 * @type {number}
526
 * @constant
527
 * @example
528
 * let secKey = SecretKey.fromSeed(new Uint8Array(32))
529
 * let sender = Address.fromKey(secKey).setPrefix('umi')
530
 * let transit = Address.fromBech32('aaa18d4z00xwk6jz6c4r4rgz5mcdwdjny9thrh3y8f36cpy2rz6emg5svsuw66')
531
 * let tx = new Transaction()
532
 *   .setVersion(Transaction.CreateTransitAddress)
533
 *   .setSender(sender)
534
 *   .setRecipient(transit)
535
 *   .sign(secKey)
536
 */
537 1
Transaction.CreateTransitAddress = 6
538
/**
539
 * Деактивация транзитного адреса.
540
 * @type {number}
541
 * @constant
542
 * @example
543
 * let secKey = SecretKey.fromSeed(new Uint8Array(32))
544
 * let sender = Address.fromKey(secKey).setPrefix('umi')
545
 * let transit = Address.fromBech32('aaa18d4z00xwk6jz6c4r4rgz5mcdwdjny9thrh3y8f36cpy2rz6emg5svsuw66')
546
 * let tx = new Transaction()
547
 *   .setVersion(Transaction.DeleteTransitAddress)
548
 *   .setSender(sender)
549
 *   .setRecipient(transit)
550
 *   .sign(secKey)
551
 */
552 1
Transaction.DeleteTransitAddress = 7
553
554
exports.Transaction = Transaction
555